#pragma once
#include "nocopy.hpp"
#include "Log.hpp"
#include "inetAddr.hpp"
#include <iostream>
#include <memory>
#include <string>
#include <functional>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
using namespace log_ns;
enum{
    SOCKET_ERROR = 1,
    BIND_ERROR
};
static const int gsockfd = -1;
static const uint16_t glocalport = 8888;
using service_t = std::function<void(int, const std::string&, InetAddr&)>; //包装器，相当于typedef，方便解耦
class UdpServer : public nocopy //继承自nocopy，禁止拷贝和赋值
{
    public:
        UdpServer(service_t func, uint16_t localport = glocalport)
        : _sockfd(gsockfd) //默认设为-1
        , _localport(localport)
        , _isrunning(false)
        , _func(func)
        {}
        void InitServer()
        {
            //::表示调用库内函数，socket创建套接字
            _sockfd = ::socket(AF_INET/*指ip网络*/, SOCK_DGRAM/*指udp通信*/, 0);
            if(_sockfd < 0)
            {
                Log(FATAL, "socket error\n");
                exit(SOCKET_ERROR);
            }
            Log(DEBUG, "socket create success, _sockfd: %d\n", _sockfd); //成功启动打印日志查看文件描述符，3号文件描述符
            struct sockaddr_in local; // 创建一个sockaddr对象设置服务器地址类型、ip地址和端口号
            memset(&local, 0, sizeof local); //内容清空重新填写
            local.sin_family = AF_INET; // 填写16位地址类型为ip
            local.sin_port = htons(_localport); //填写设置的端口号，htons将一个无符号短整型数值转换为网络字节序，即大端模式
            //local.sin_addr.s_addr = inet_addr("_localip.c_str()"); //需要4字节ip，需要网络字节序的ip,inet_addr函数正好做这两个工作
            local.sin_addr.s_addr = INADDR_ANY; //INADDR_ANY即任意网络地址，实际就是0
            int n = ::bind(_sockfd, (struct sockaddr*)&local, sizeof local); //跟系统内核做绑定，正常返回0，失败返回-1
            if(n < 0)
            {
                Log(FATAL, "bind error");
                exit(BIND_ERROR);
            }
            Log(DEBUG, "socket bind success\n");
        }
        void Start()
        {
            _isrunning = true;
            char message[1024]; //设置缓冲区
            while(_isrunning)
            {
                sleep(1);
                struct sockaddr_in peer; //接受客户端信息
                socklen_t len = sizeof peer;
                int n = recvfrom(_sockfd, message, (sizeof message) - 1, 0, (struct sockaddr*)&peer, &len); //接受客户端发来的信息，把客户端的sockaddr_in结构体（即套接字信息）信息填入到peer
                //std::cout << "peer.sin_family: " << peer.sin_family << std::endl;
                //std::cout << "ip: " << inet_ntoa(peer.sin_addr) << std::endl;
                //std::cout << "port: " << ntohs(peer.sin_port) << std::endl;
                if(n > 0)
                {
                    //uint16_t port = ntohs(peer.sin_port);
                    //std::string ip = inet_ntoa(peer.sin_addr);
                    InetAddr addr(peer); //存储客户端信息
                    message[n] = 0;
                    //std::cout << "client say: " << inbuffer << std::endl;
                    //std::cout << "[" << addr.Ip() << ":" << addr.Port() << "]# " << message << std::endl;
                    Log(DEBUG, "[%s]# %s\n", addr.AddrStr().c_str(), message);
                    _func(_sockfd, message, addr); //回调函数，做消息转发
                    Log(DEBUG, "return dupserver\n");
                }
                else
                    std::cerr << "recvfrom error" << std::endl;
            }
            _isrunning = false;
        }
        ~UdpServer(){
            if(_sockfd > gsockfd)
                ::close(_sockfd); //如果文件描述符大于-1，关闭服务器文件
        }
    private:
        int _sockfd; //套接字文件描述符，读写都用同一个sockfd，说明UDP是全双工通信的
        uint16_t _localport; //端口号
        //std::string _localip; //ip地址
        bool _isrunning = false; //标记服务器是否启动
        service_t _func; //回调函数，做消息转发
};